package com.dtp.core.notify;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.dtp.common.em.NotifyItemEnum;
import com.dtp.common.em.NotifyPlatformEnum;
import com.dtp.common.entity.AlarmInfo;
import com.dtp.common.entity.TpMainFields;
import com.dtp.common.entity.NotifyItem;
import com.dtp.common.entity.NotifyPlatform;
import com.dtp.common.util.CommonUtil;
import com.dtp.core.context.AlarmCtx;
import com.dtp.core.context.BaseNotifyCtx;
import com.dtp.core.context.DtpNotifyCtxHolder;
import com.dtp.core.notify.alarm.AlarmCounter;
import com.dtp.core.notify.base.Notifier;
import com.dtp.core.support.ExecutorWrapper;
import com.dtp.core.thread.DtpExecutor;
import com.dtp.core.thread.ExecutorAdapter;
import com.google.common.base.Joiner;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.MDC;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.dtp.common.constant.DynamicTpConst.TRACE_ID;
import static com.dtp.common.constant.DynamicTpConst.UNKNOWN;
import static com.dtp.common.constant.LarkNotifyConst.LARK_AT_FORMAT_OPENID;
import static com.dtp.common.constant.LarkNotifyConst.LARK_AT_FORMAT_USERNAME;
import static com.dtp.common.constant.LarkNotifyConst.LARK_OPENID_PREFIX;
import static com.dtp.core.notify.manager.NotifyHelper.getAlarmKeys;
import static com.dtp.core.notify.manager.NotifyHelper.getAllAlarmKeys;

/**
 * 抽象的dynamic-tp的通知者实现类
 *
 * @author yanhom
 * @since 1.0.0
 *
 * @remark muse
 **/
@Slf4j
public abstract class AbstractDtpNotifier implements DtpNotifier {

    protected Notifier notifier; // 通知器

    protected AbstractDtpNotifier() {}

    protected AbstractDtpNotifier(Notifier notifier) {this.notifier = notifier;}

    /** 发送变更的消息通知 */
    @Override
    public void sendChangeMsg(NotifyPlatform notifyPlatform, TpMainFields oldFields, List<String> diffs) {
        String content = buildNoticeContent(notifyPlatform, oldFields, diffs);
        if (StringUtils.isBlank(content)) return; // 通知内容是空的，则不执行通知操作
        notifier.send(notifyPlatform, content);
    }

    /** 发送警告消息 */
    @Override
    public void sendAlarmMsg(NotifyPlatform notifyPlatform, NotifyItemEnum notifyItemEnum) {
        String content = buildAlarmContent(notifyPlatform, notifyItemEnum);
        if (StringUtils.isBlank(content)) return; // 警报内容是空的，则不执行通知操作
        notifier.send(notifyPlatform, content);
    }

    /** 获得通知模版 */
    protected abstract String getNoticeTemplate();

    /** 获得警告模版 */
    protected abstract String getAlarmTemplate();

    /** 获得内容配色（left: highlight color, right: other content color）*/
    protected abstract Pair<String, String> getColors();

    /** 构建报警内容 */
    protected String buildAlarmContent(NotifyPlatform platform, NotifyItemEnum notifyItemEnum) {
        AlarmCtx context = (AlarmCtx) DtpNotifyCtxHolder.get();
        ExecutorWrapper executorWrapper = context.getExecutorWrapper();
        String threadPoolName = executorWrapper.getThreadPoolName();
        val executor = executorWrapper.getExecutor();
        NotifyItem notifyItem = context.getNotifyItem();
        AlarmInfo alarmInfo = context.getAlarmInfo();
        val alarmCounter = AlarmCounter.countStrRrq(threadPoolName, executor);

        // 从spring.dynamic.tp.platforms.platform.receivers配置中获得接受者
        String receivesStr = getReceives(platform.getPlatform(), platform.getReceivers());
        String content = String.format(
                getAlarmTemplate(),                                                             // 获得警告模版
                CommonUtil.getInstance().getServiceName(),                                      // 服务名称
                CommonUtil.getInstance().getIp() + ":" + CommonUtil.getInstance().getPort(),    // ip：port
                CommonUtil.getInstance().getEnv(),  // 环境
                populatePoolName(executorWrapper),  // 线程池名称
                notifyItemEnum.getValue(),          // 报警类型
                notifyItem.getThreshold(),          // 报警阈值
                executor.getCorePoolSize(),         // 核心线程数
                executor.getMaximumPoolSize(),      // 最大线程数
                executor.getPoolSize(),             // 当前线程数
                executor.getActiveCount(),          // 活跃线程数
                executor.getLargestPoolSize(),      // 历史最大线程数
                executor.getTaskCount(),            // 任务总数
                executor.getCompletedTaskCount(),   // 执行完成任务数
                executor.getQueue().size(),         // 等待执行任务数
                executor.getQueue().getClass().getSimpleName(), // 队列类型
                getQueueCapacity(executor),                     // 队列容量
                executor.getQueue().size(),                     // 队列任务数量
                executor.getQueue().remainingCapacity(),        // 队列剩余容量
                getRejectHandlerName(executor), // 拒绝策略
                alarmCounter.getLeft(),         // 拒绝任务数量比例
                alarmCounter.getMiddle(),       // 运行超时任务数量比例
                alarmCounter.getRight(),        // 等待队列超时任务数量比例
                Optional.ofNullable(alarmInfo.getLastAlarmTime()).orElse(UNKNOWN),  // 上次报警时间
                DateUtil.now(),                                                     // 报警时间
                receivesStr,                                                        // 接收人
                Optional.ofNullable(MDC.get(TRACE_ID)).orElse(UNKNOWN),             // tid
                notifyItem.getInterval()                                            // 报警间隔
        );
        return highlightAlarmContent(content, notifyItemEnum); // 高亮
    }

    /** 构建通知内容 */
    protected String buildNoticeContent(NotifyPlatform platform, TpMainFields oldFields, List<String> diffs) {
        BaseNotifyCtx context = DtpNotifyCtxHolder.get();
        ExecutorWrapper executorWrapper = context.getExecutorWrapper();
        val executor = executorWrapper.getExecutor();
        String receivesStr = getReceives(platform.getPlatform(), platform.getReceivers());

        String content = String.format(
                getNoticeTemplate(),                        // 通知模版
                CommonUtil.getInstance().getServiceName(),  // 服务名称
                CommonUtil.getInstance().getIp() + ":" + CommonUtil.getInstance().getPort(),// ip：port
                CommonUtil.getInstance().getEnv(),                                          // 环境
                populatePoolName(executorWrapper),                                          // 线程池名称
                oldFields.getCorePoolSize(), executor.getCorePoolSize(),                    // 核心线程数
                oldFields.getMaxPoolSize(), executor.getMaximumPoolSize(),                  // 最大线程数
                oldFields.isAllowCoreThreadTimeOut(), executor.allowsCoreThreadTimeOut(),   // 允许核心线程超时
                oldFields.getKeepAliveTime(), executor.getKeepAliveTime(TimeUnit.SECONDS),  // 线程存活时间
                executor.getQueue().getClass().getSimpleName(),             // 队列类型
                oldFields.getQueueCapacity(), getQueueCapacity(executor),   // 队列容量
                oldFields.getRejectType(), getRejectHandlerName(executor),  // 拒绝策略
                receivesStr,    // 接收人
                DateTime.now()  // 通知时间
        );
        return highlightNotifyContent(content, diffs); // 高亮
    }

    /** 获得通知接收人 */
    private String getReceives(String platform, String receives) {
        if (StringUtils.isBlank(receives)) return "";

        // 针对"lark"通知封装接收人
        if (NotifyPlatformEnum.LARK.name().toLowerCase().equals(platform)) {
            return Arrays.stream(receives.split(","))
                    .map(receive -> StringUtils.startsWith(receive, LARK_OPENID_PREFIX) // 是否"ou_"开头
                            ? String.format(LARK_AT_FORMAT_OPENID, receive) // "<at id='%s'></at>", 当配置openid时,机器人可以@人
                            : String.format(LARK_AT_FORMAT_USERNAME, receive)) // "<at id=''>%s</at>", 当配置username时,只能蓝色字体展示@username,被@人无@提醒
                    .collect(Collectors.joining(" "));
        }

        // 针对非"lark"通知封装接收人
        else {
            String[] receivers = StringUtils.split(receives, ',');
            return Joiner.on(", @").join(receivers); // 逗号分割，并且接收人前面加'@'
        }
    }

    /** 获得线程池名称 */
    protected String populatePoolName(ExecutorWrapper executorWrapper) {
        String poolAlisaName; // 获得线程池别名
        if (executorWrapper.getExecutor() instanceof DtpExecutor)
            poolAlisaName = ((DtpExecutor) executorWrapper.getExecutor()).getThreadPoolAliasName();
        else
            poolAlisaName = executorWrapper.getThreadPoolAliasName();

        // 如果poolAlisaName没有设置，则直接返回线程池名称
        if (StringUtils.isBlank(poolAlisaName)) return executorWrapper.getThreadPoolName();

        return executorWrapper.getThreadPoolName() + "(" + poolAlisaName + ")";
    }

    /** 获得拒绝策略名称 */
    protected String getRejectHandlerName(ExecutorAdapter<?> executor) {return executor.getRejectHandlerName();}

    /** 获得队列容量 */
    protected int getQueueCapacity(ExecutorAdapter<?> executor) {
        if (executor instanceof DtpExecutor) return ((DtpExecutor) executor).getQueueCapacity();

        return executor.getQueue().size() + executor.getQueue().remainingCapacity();
    }

    /** 获得高亮的通知内容 */
    private String highlightNotifyContent(String content, List<String> diffs) {
        if (StringUtils.isBlank(content)) return content;

        Pair<String, String> pair = getColors(); // 获得颜色，pair=[高亮颜色, 其他内容颜色]
        for (String field : diffs) content = content.replace(field, pair.getLeft());
        for (Field field : TpMainFields.getMainFields()) content = content.replace(field.getName(), pair.getRight());
        return content;
    }

    /** 获得高亮的警告内容 */
    private String highlightAlarmContent(String content, NotifyItemEnum notifyItemEnum) {
        if (StringUtils.isBlank(content)) return content;

        Set<String> colorKeys = getAlarmKeys(notifyItemEnum);
        Pair<String, String> pair = getColors();
        for (String field : colorKeys) content = content.replace(field, pair.getLeft());
        for (String field : getAllAlarmKeys()) content = content.replace(field, pair.getRight());
        return content;
    }
}
